/*
*	CLASE CAJA
*	Definición
*/

#ifndef _BOX_H_
#define _BOX_H_

#include "constantes.h"

using namespace std;

typedef long double LONGD;

class Box;
extern double W_LIMIT;

class Type
{
public:
	Type(){}
	Type(char &name,double min,double max, double w_lim);
	bool operator==(const Type &t){return this->nombre == t.nombre;}
	bool operator!=(const Type &t){return this->nombre != t.nombre;}
	
	char getName(){return nombre;}
	double getMax(){return max;}
	double getMin(){return min;}
	double getWLim(){return w_lim;}

	bool puedeSoportarTipo(Type &t);

	void addRestriction(char &tipo);

private:
	char nombre;
	double min, max, w_lim;
	vector<char> restricciones;
};


Type::Type(char &name,double min,double max, double w_lim)
{
	nombre=name;
	this->min=min;
	this->max=max;
	if(w_lim<=W_LIMIT)
		this->w_lim=w_lim;
	else
		this->w_lim=W_LIMIT;
}

void Type::addRestriction(char &tipo)
{
	restricciones.push_back(tipo);
}


bool Type::puedeSoportarTipo(Type &t)
{
	for(int i=0; i<restricciones.size(); ++i)
		if(restricciones[i]==t.nombre)
			return true;

	return false;
}



extern vector<Type> TIPOS;

class Box
{
public:
	Box() {};
	Box(int , int , int , double , string, string, string, string);
	Box(const Box &);
	~Box() {};
	Box& operator=(const Box &b);
	bool operator==(const Box &b){return this->id == b.id;}
	bool operator!=(const Box &b){return this->id != b.id;};
	friend ostream &operator<< (ostream &, Box &);
	bool operator<=(const Box &b);
	bool operator<(const Box &b);
	bool operator>(const Box &b);

	void setX(int x) {this->x=x;}
	int getX()	{return x;}
	void setY(int y) {this->y=y;}
	int getY()	{return y;}
	void setZ(int z) {this->z=z;}
	int getZ()	{return z;}

	Type getType() {return type;}
	char getNameType(){return type.getName();}
	void setType(Type t) {type=t;}

	void assignType();

	string getName() {return name;}
	string getIdBase(){return id_base;}
	string getClient() {return client;}
	string getDestination() {return destination;}
	string getId() {return id;}

	double getWeight(){return weight;}
	void setWeight(double w){weight=w;}

	double getSuppWeight(){return supp_weight;}
	void setSuppWeight(double w){supp_weight=w;}
	void addWeight(double w);
	
	int getWidth() {return width;}  
	void setWidth(int w) {this->width=w;}

	int getHeight(){return height;}
	void setHeight(int h){height=h;}

	int	getDepth(){return depth;}
	void setDepth(int d) {this->depth=d;}

	LONGD getVolume(){return volume;}

	Box * getBase(string name);
	Box * getBase() { return base; }
	void setBase(Box * b) { base = b; 
							if(b!=NULL) 
								id_base = b->id;}

	

	bool puedeSoportarA(Box &, double);
	
	bool getEsBase(){return es_base;}
	void setEsBase(bool b){es_base=b;}

	bool getFragil(){return es_fragil;}
	void setFragil(bool b){es_fragil=b;}

	void turnBox()
	{
		int aux;
		this->girada=!this->girada;
		aux=this->width;
		this->width=this->depth;
		this->depth=aux;
	}

	bool combinada;

private:

	string id,
		 name,
         client,
		 destination;
	int	x,
		y,
		z,
	    width,   //valores en mm
		height,
		depth; 

      Type type;

	double weight,
		   supp_weight;	//Peso soportado por la caja

       LONGD volume;  //valor en mm^3
	
      string id_base;
	Box * base;	

	bool es_base; //true si tiene cajas encima

	bool girada;	//TRUE si la caja se coloca girada 90º

	bool es_fragil;	//TRUE si la caja contiene piezas frágiles y debe ir en posiciones altas
};





Box::Box(int width, int height, int depth, double weight, string name, string client, string destination, string id)
{
	this->weight=weight;
	this->supp_weight=0;
	this->width = width;
	this->height = height;
	this->depth = depth;
	this->name = name;
	this->client = client;
	this->destination = destination;
	this->id=id;
	this->volume = (LONGD)width * (LONGD)height * (LONGD)depth;
	this->x=this->y=this->z=-1; //La caja no está colocada

	this->base=NULL;
	this->es_base=false;
	this->girada=false;
	this->es_fragil=false;
	this->combinada=false;

	this->assignType();
}

Box::Box(const Box &b)
{
	this->width=b.width;
	this->height=b.height;
	this->depth=b.depth;
	this->weight=b.weight;
	this->supp_weight=b.supp_weight;
	this->name=b.name;
	this->client=b.client;
	this->destination=b.destination;
	this->id=b.id;
	this->volume=b.volume;
	this->x = b.x;
	this->y = b.y;
	this->z = b.z;
	this->type = b.type;
	this->base = b.base;
	this->id_base = b.id_base;
	this->es_base = b.es_base;
	this->girada = b.girada;
	this->es_fragil = b.es_fragil;
	this->combinada = b.combinada;
}

Box& Box::operator=(const Box &b)
{
	this->width=b.width;
	this->height=b.height;
	this->depth=b.depth;
	this->weight=b.weight;
	this->supp_weight=b.supp_weight;
	this->name=b.name;
	this->client=b.client;
	this->destination=b.destination;
	this->id=b.id;
	this->volume=b.volume;
	this->x = b.x;
	this->y = b.y;
	this->z = b.z;
	this->type = b.type;
	this->base = b.base;
	this->id_base = b.id_base;
	this->es_base = b.es_base;
	this->girada = b.girada;
	this->es_fragil = b.es_fragil;
	this->combinada = b.combinada;
	
	return *this;
}

void Box::assignType()
{
	/*for(int i=0; i<TIPOS.size(); ++i)
		if(this->weight > TIPOS[i].getMin()
			&& this->weight <= TIPOS[i].getMax())
		{
			this->type = TIPOS[i];
			return;
		}*/

		if(this->weight > 500)
		{
			this->type = TIPOS[0];

		}
	if(this->weight > 250
			&& this->weight <= 500)
		{
			this->type = TIPOS[1];
	
		}
	if(this->weight <= 250)
		{
			this->type = TIPOS[2];
			
		}
}

/*
*	Imprime las coordenadas y los tamaños de las cajas en METROS
*/
ostream & operator<<(ostream & out, Box &b)
{	
	out<<b.x/1000.0<<"\t"<<b.y/1000.0<<"\t"<<b.z/1000.0<<"\t"<<b.width/1000.0<<"\t"<<b.height/1000.0<<"\t"<<b.depth/1000.0<<"\t"<<b.type.getName();
	return out;
}


bool Box::operator<=(const Box &b)
{
	return this->volume<=b.volume;
}

bool Box::operator<(const Box &b)
{
	return this->volume<=b.volume;
}



bool Box::operator>(const Box &b)
{
	return this->volume>b.volume;
}

/*
*	Retorna cierto si 'caja' puede colocarse encima de 'this'
*
*	'this' soporta a 'caja' sii:
*		#'this' no es una caja con piezas frágiles
*		#siendo del mismo tipo, 'this' pesa más que 'caja'
*		#siendo 'this' de tipo 'M', 'caja' es de tipo 'M' o 'H'
*		#siendo 'this' de tipo 'H', 'caja' es de tipo 'H'
*		#'caja' tiene un anchoXlargo menor o igual que 'this' (hay giros de 90º)
*		#apilando 'caja' no se supera el 'w_limit' soportado por 'this'
*		#apilando 'caja' no se supera el 'h_limit' (altura límite)
*		#teniendo base 'this', aquélla soporta también a 'caja'
*
*/
bool Box::puedeSoportarA(Box &caja, double h_limit)
{
	if(this->es_fragil)
		return false;

	if(!caja.combinada
		&& this->type==caja.getType()
		&& caja.getWeight() > this->weight)
		return false;

	Type tipo = caja.getType();
	if(!caja.combinada
		&& !this->type.puedeSoportarTipo(tipo))
		return false;

	if(caja.getWidth()>this->width 
		|| caja.getDepth()>this->depth)
	{
		//Intentar girarla
		if(caja.getWidth()<=this->depth
			&& caja.getDepth()<=this->width)
		{
			caja.turnBox();
		}
		else
			return false;
	}
	
	if(caja.getWeight()+this->supp_weight > this->type.getWLim())
		return false;

	if(this->y+this->height+caja.getHeight() > h_limit)
		return false;

	Box combinada(this->width,this->height+caja.getHeight(),this->depth,caja.getWeight(),"","","","");
	combinada.setType(this->type);
	combinada.combinada=true;
	if(this->base!=NULL &&
		!this->base->puedeSoportarA(combinada,h_limit))
			return false;

	return true;
}


/*
*	Añade 'w' al peso soportado por la caja
*	Si la caja estuviese sobre otra (tiene base),
*	'w' también se añade a ésta.
*/
void Box::addWeight(double w)
{
	this->supp_weight+=w;
	if(this->base!=NULL)
	{
		this->base->addWeight(w);
	}
}

bool noChocaZ(int ox, int oy, int oz, Box *caja, list<Box> &colocadas, int colocadas_size)
{
	list<Box>::iterator it_col=colocadas.begin();
	for(int i=0; i<colocadas_size; ++i, ++it_col)
	{
		if(oy < (*it_col).getY()+(*it_col).getHeight())
		{
			if(oz == (*it_col).getZ()+(*it_col).getDepth())
			//La caja colocada está a la altura de la que queremos colocar
				if((ox < (*it_col).getX()+(*it_col).getWidth()
					&& ox+caja->getWidth() >= (*it_col).getX()+(*it_col).getWidth())
					||
					((*it_col).getX() < ox+caja->getWidth()
					&& (*it_col).getX()+(*it_col).getWidth() >= ox+caja->getWidth()))
					return false;
		}
	}

	return true;
}


bool noChocaX(int ox, int oy, int oz, Box *caja, list<Box> &colocadas, int colocadas_size)
{
	list<Box>::iterator it_col=colocadas.begin();
	for(int i=0; i<colocadas_size; ++i, ++it_col)
	{
		if(oy < (*it_col).getY()+(*it_col).getHeight())
		{
			if(ox == (*it_col).getX()+(*it_col).getWidth())
			//La caja colocada está a la altura de la que queremos colocar
				if((oz < (*it_col).getZ()+(*it_col).getDepth()
					&& oz+caja->getDepth() >= (*it_col).getZ()+(*it_col).getDepth())
					||
					((*it_col).getZ() < oz+caja->getDepth()
					&& (*it_col).getZ()+(*it_col).getDepth() >= oz+caja->getDepth()))
					return false;
		}
	}

	return true;
}


bool noChocaZ_2D(int ox, int oz, Box *caja, list<Box> &colocadas, int colocadas_size)
{
	list<Box>::iterator it_col=colocadas.begin();
	for(int i=0; i<colocadas_size; ++i, ++it_col)
	{
			if(oz == (*it_col).getZ()+(*it_col).getDepth())
			//La caja colocada está a la altura de la que queremos colocar
				if((ox < (*it_col).getX()+(*it_col).getWidth()
					&& ox+caja->getWidth() >= (*it_col).getX()+(*it_col).getWidth())
					||
					((*it_col).getX() < ox+caja->getWidth()
					&& (*it_col).getX()+(*it_col).getWidth() >= ox+caja->getWidth()))
					return false;
	}

	return true;
}


bool noChocaX_2D(int ox, int oz, Box *caja, list<Box> &colocadas, int colocadas_size)
{
	list<Box>::iterator it_col=colocadas.begin();
	for(int i=0; i<colocadas_size; ++i, ++it_col)
	{
			if(ox == (*it_col).getX()+(*it_col).getWidth())
			//La caja colocada está a la altura de la que queremos colocar
				if((oz < (*it_col).getZ()+(*it_col).getDepth()
					&& oz+caja->getDepth() >= (*it_col).getZ()+(*it_col).getDepth())
					||
					((*it_col).getZ() < oz+caja->getDepth()
					&& (*it_col).getZ()+(*it_col).getDepth() >= oz+caja->getDepth()))
					return false;
	}

	return true;
}


#endif